/*
 * main.c
 * 
 * Copyright(c) 2021 Cai_XL <Cai_XL@outlook.com>
 * bilibili : https://space.bilibili.com/54910927
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
#include "main.h"

//#define USE_UART0
// use UART0 for this example
// UART0_RX:PE0
// UART0_TX:PE1

#define USE_UART1
// use UART1 for this example
// UART1_RX:PA2
// UART1_TX:PA3

//#define USE_UART2
// use UART2 for this example
// UART2_RX:PE8
// UART2_TX:PE7

static void CCU_Init(void);
static void UART_Init(void);
static void UART_SendByte(uint8_t);
static void UART_SendString(uint8_t*);

UART_TypeDef *UARTx = 0;    // the UART for example

int main(void)
{
    CCU_Init();     // initialize CCU
    UART_Init();    // initialize UART

    while(1)
    {
        // send string
        UART_SendString((uint8_t*)"\r\nHello from F1C200S\r\n");

        // simple delay
        {
            volatile uint32_t i = 5000000;
            while(i--);
        }
    }
}

// CPUCLK:408MHz
// AHBCLK:200MHz
// APBCLK:100MHz
static void CCU_Init(void)
{
    uint32_t timeout;						// variable to wait PLL stable

	CCU->AHB_APB_HCLKC_CFG_REG &= ~(3<<8);	// APB_CLK_DIV_RATIO = 2
	CCU->AHB_APB_HCLKC_CFG_REG &= ~(3<<4);	// AHB_CLK_DIV_RATIO = 1
	CCU->AHB_APB_HCLKC_CFG_REG |= (2<<6);	//AHB_PRE_DIV = 3

	/* enable PLL_PERIPH */
	CCU->PLL_GLOBAL_TIME_REG = 0x1ff;		// PLL lock time
	CCU->PLL_PERIPH_CTRL_REG |= (1<<31);	// enable PLL_PERIPH in 600MHz(default)
	timeout = 0xfff;
	while((timeout--) && !(CCU->PLL_PERIPH_CTRL_REG & (1<<28)));	// wait PLL_PERIPH stable

	CCU->AHB_APB_HCLKC_CFG_REG |= (3<<12);	// AHB_CLK_SRC = PLL_PERIPH / AHB_PRE_DIV

	CCU->PLL_CPU_TIME_REG = 0x1ff;			// PLL lock time
	CCU->PLL_CPU_CTRL_REG |= (1<<31);		// enable PLL_CPU in 408MHz(default)
	timeout = 0xfff;
	while((timeout--) && !(CCU->PLL_CPU_CTRL_REG & (1<<28)));	// wait PLL_CPU stable

	CCU->CPU_CLK_SRC_REG |= (1<<17);		// CPUCLK = PLL_CPU

    timeout = 0xfff;
    while(timeout--);                       // wait clock stble
}

static void UART_Init(void)
{

    #if defined(USE_UART1) 
    
        GPIOA->Pn_CFG0 &= ~0xff00;  // reset multiplexing functionses
        GPIOA->Pn_CFG0 |= 0x5500;   // remap to UART1
        //clock config
        CCU->APB1_CLK_GATING_REG |= (1<<21);    // enable UART0 clock
        NOP();                                  // wait clock stable
        NOP();
        NOP();
        NOP();
        CCU->APB1_SOFT_RST_REG |= (1<<21);      // de-assert UART0 reset

        UARTx = UART1;              // UART0 is selected


    #elif defined(USE_UART2) 
        // GPIO config
        GPIOE->Pn_CFG0 &= ~(0x0f << (4*7));    // reset multiplexing functionses 
        GPIOE->Pn_CFG1 &= ~0x0f;
        GPIOE->Pn_CFG0 |= (0x03 << (4*7));     // remap to UART2
        GPIOE->Pn_CFG1 |= 0x03;
        GPIOE->Pn_PUL0 &= ~(0x0f << (2*7));    // reset pull-up & pull-down
        GPIOE->Pn_PUL0 |= (0x05 << (2*7));     // pull-up

        //clock config
        CCU->APB1_CLK_GATING_REG |= (1<<22);    // enable UART0 clock
        NOP();                                  // wait clock stable
        NOP();
        NOP();
        NOP();
        CCU->APB1_SOFT_RST_REG |= (1<<22);      // de-assert UART0 reset

        UARTx = UART2;              // UART0 is selected

    #else

        #ifndef USE_UART0
            #warning "UART0 is selected in default !"
        #endif

        // GPIO config
        GPIOE->Pn_CFG0 &= ~0xff;    // ret multiplexing functionses
        GPIOE->Pn_CFG0 |= 0x55;     // remap to UART0
        // Pn_CFG0[3:0] : 101 -> PE0 as UART0_RX
        // Pn_CFG0[7:4] : 101 -> PE1 as UART0_TX

        GPIOE->Pn_PUL0 &= ~0x0f;    // reset pull-up & pull-down
        GPIOE->Pn_PUL0 |= 0x05;     // pull-up
        // Pn_PUL0[2n+1:2n] : 10 -> PEn pull-up

        //clock config
        CCU->APB1_CLK_GATING_REG |= (1<<20);    // enable UART0 clock
        // APB1_CLK_GATING_REG[20] : UART0_GATING
        // 0 : Mask
        // 1 : pass
        NOP();                                  // wait clock stable
        NOP();
        NOP();
        NOP();
        CCU->APB1_SOFT_RST_REG |= (1<<20);      // de-assert UART0 reset
        // APB1_CLK_GATING_REG[20] : UART0_RST
        // 0 : assert
        // 1 : de-assert

        UARTx = UART0;              // UART0 is selected

    #endif

    // 115200bps, 8bit data, 1stopbit, no parity

    UARTx->Group2.UART_IER_REG = 0x00;  // disable all interrupt
    UARTx->UART_MCR_REG = 0x00;         // reset Modem

    // set divisor of buadrate
    // Divisor Latch : [UART_DLH_REG:UART_DLL_REG]
    // formula : Divisor Latch = APBCLK/(buadrate*16)
    UARTx->UART_LCR_REG |= (1<<7);      // set DLAB to 1
    UARTx->Group1.UART_DLL_REG = (100000000/(115200*16)) & 0xff;
    UARTx->Group2.UART_DLH_REG = (100000000/(115200*16)) >> 8;
    UARTx->UART_LCR_REG &= ~(1<<7);     // set DLAB to 0

    UARTx->UART_LCR_REG &= ~0x2f;       // reset parity,stopbit,databit
    UARTx->UART_LCR_REG |= 0x03;        // 8bit of data, no parity, 1 stopbit 
    // UART_LCR_REG[3] : PEN
    // 0 : disable parity
    // 1 : enable parity
    // UART_LCR_REG[2] : STOP
    // 0 : 1 stopbit
    // 1 : if UART_LCR_REG[1:0] == 0 then 1.5 stopbit, else 2 stopbit
    // UART_LCR_REG[1:0] : DLS
    // 00 : 5 bit
    // 01 : 6 bit
    // 10 : 7 bit
    // 11 : 8 bit
}

static void UART_SendByte(uint8_t data)
{
    if(!UARTx) return;   // only if UARTx is valid 

    while(!(UARTx->UART_LSR_REG & (1<<5)));
    // UART_LSR_REG[5] : THRE
    // 1 : THR is ready to accept new byte
    // 0 : THR not valid
    UARTx->Group1.UART_THR_REG = data;
}

static void UART_SendString(uint8_t* str)
{
    while(*str)
    {
        UART_SendByte(*str);    // byte by byte
        str++;
    }
}